package org.rsbot.script.wrappers;

import org.rsbot.client.*;
import org.rsbot.script.methods.MethodContext;
import org.rsbot.script.methods.MethodProvider;

import java.awt.*;

public abstract class RSCharacter extends MethodProvider implements RSTarget, RSLocatable, RSInteractable, RSVerifiable {
    public RSCharacter(final MethodContext ctx) {
        super(ctx);
    }

    /**
     * Retrieves a reference to the client accessor. For internal use. The
     * reference should be stored in a SoftReference by subclasses to allow for
     * garbage collection when appropriate.
     *
     * @return The client accessor.
     */
    public abstract org.rsbot.client.RSCharacter getAccessor();

    public abstract int getLevel();

    public abstract String getName();

    /**
     * Performs an action on a humanoid character (tall and skinny).
     *
     * @param action The action of the menu entry to be clicked (if available).
     * @return <tt>true</tt> if the option was found; otherwise <tt>false</tt>.
     */
    public boolean interact(final String action) {
        return interact(action, null);
    }

    /**
     * Performs an action on a humanoid character (tall and skinny).
     *
     * @param action The action of the menu entry to be clicked (if available).
     * @param option The option of the menu entry to be clicked (if available).
     * @return <tt>true</tt> if the option was found; otherwise <tt>false</tt>.
     */
    public boolean interact(final String action, final String option) {
        final RSModel model = getModel();
        if (model != null) {
            return model.interact(action, option);
        } else {
            for (byte i = 0; i < 10; i++) {
                final RSTile location = getLocation();
                if (methods.tiles.interact(location, action, option)) {
                    return true;
                }
            }
        }
        return false;
    }

    public int getAnimation() {
        final RSAnimator a = getAccessor().getAnimation();
        if (a != null) {
            final RSSequence s = a.getSequence();
            if (s != null) {
                return s.getID();
            }
        }
        return -1;
    }

    public int getPassiveAnimation() {
        final RSAnimator a = getAccessor().getPassiveAnimation();
        if (a != null) {
            final RSSequence s = a.getSequence();
            if (s != null) {
                return s.getID();
            }
        }
        return -1;
    }

    public int getGraphic() {
        return getAccessor().getGraphicsData()[0].getID();
    }

    public int getHeight() {
        return getAccessor().getHeight();
    }

    /**
     * @return The % of HP remaining
     */
    public int getHPPercent() {
        return isInCombat() ? getAccessor().getHPRatio() * 100 / 255 : 100;
    }

    public RSCharacter getInteracting() {
        final int interact = getAccessor().getInteracting();
        if (interact == -1) {
            return null;
        }
        if (interact < 32768) {
            final Node node = methods.nodes.lookup(methods.client.getRSNPCNC(), interact);
            if (node == null || !(node instanceof RSNPCNode)) {
                return null;
            }
            return new RSNPC(methods, ((RSNPCNode) node).getRSNPC());
        } else {
            int index = interact - 32768;
            if (index == methods.client.getSelfInteracting()) {
                index = 2047;
            }
            return new RSPlayer(methods, methods.client.getRSPlayerArray()[index]);
        }
    }

    public RSTile getLocation() {
        final org.rsbot.client.RSCharacter c = getAccessor();
        if (c == null) {
            return new RSTile(-1, -1);
        }
        final int x = methods.client.getBaseX() + (c.getX() >> 9);
        final int y = methods.client.getBaseY() + (c.getY() >> 9);
        return new RSTile(x, y, methods.game.getPlane());
    }

    public String getMessage() {
        final RSMessageData messageData = getAccessor().getMessageData();
        return messageData != null ? messageData.getMessage() : null;
    }

    /**
     * Gets the minimap location, of the character. Note: This does work when
     * it's walking!
     *
     * @return The location of the character on the minimap.
     */
    public Point getMinimapLocation() {
        final org.rsbot.client.RSCharacter c = getAccessor();
        final int cX = methods.client.getBaseX() + (c.getX() / 32 - 2) / 4;
        final int cY = methods.client.getBaseY() + (c.getY() / 32 - 2) / 4;
        return methods.calc.worldToMinimap(cX, cY);
    }

    public RSModel getModel() {
        final org.rsbot.client.RSCharacter c = getAccessor();
        if (c != null) {
            final Model model = c.getModel();
            if (model != null) {
                return new RSCharacterModel(methods, model, c);
            }
        }
        return null;
    }

    public int getOrientation() {
        return (180 + (getAccessor().getOrientation() * 45 / 2048)) % 360;
    }

    public Point getScreenLocation() {
        final org.rsbot.client.RSCharacter c = getAccessor();
        final RSModel model = getModel();
        if (model == null) {
            return methods.calc.groundToScreen(c.getX(), c.getY(),
                    c.getHeight() / 2);
        } else {
            return model.getPoint();
        }
    }

    /**
     * Hovers this Player/NPC
     */
    public void hover() {
        final RSModel model = getModel();
        if (model != null)
            model.hover();
    }

    public boolean isInCombat() {
        return methods.game.isLoggedIn()
                && methods.client.getLoopCycle() < getAccessor()
                .getLoopCycleStatus();
    }

    /**
     * Determines whether the character is dead or dying
     *
     * @return <tt>true</tt> if the character is dead/dying; otherwise
     *         <tt>false</tt>.
     */
    public boolean isDead() {
        return !isValid() || getAnimation() == 836;
    }

    public boolean isInteractingWithLocalPlayer() {
        return getAccessor().getInteracting() - 32768 == methods.client
                .getSelfInteracting();
    }

    public boolean isMoving() {
        return getAccessor().isMoving() != 0;
    }

    public boolean isOnScreen() {
        final RSModel model = getModel();
        if (model == null) {
            return methods.calc.tileOnScreen(getLocation());
        } else {
            return methods.calc.pointOnScreen(model.getPoint());
        }
    }

    public boolean isValid() {
        return getAccessor() != null;
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof RSCharacter) {
            final RSCharacter cha = (org.rsbot.script.wrappers.RSCharacter) obj;
            return cha.getAccessor() == getAccessor();
        }
        return false;
    }

    @Override
    public int hashCode() {
        return System.identityHashCode(getAccessor());
    }

    @Override
    public String toString() {
        final RSCharacter inter = getInteracting();
        final String msg = getMessage();
        return "[anim=" + getAnimation()
                + (msg != null ? ",msg=" + getMessage() : "")
                + ",interact=" + (inter == null ? "null" :
                inter.isValid() ? inter.getName() : "Invalid") + "]";
    }

    public Point getPoint() {
        return getScreenLocation();
    }

    public boolean contains(int x, int y) {
        RSModel model = getModel();
        if (model != null) {
            return model.contains(x, y);
        }
        return getScreenLocation().distance(x, y) < random(0, 8);
    }
}